package hehe.container.scan;

import hehe.container.Container;
import hehe.container.base.ResourcePath;

import java.io.File;
import java.io.FileInputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 扫描类文件
 *<B>说明：</B>
 *<pre>
 * 略 类详细描述
 *</pre>
 *<B>示例：</B>
 *<pre>
 *  略
 *</pre>
 *<B>日志：</B>
 *<pre>
 *  略
 *</pre>
 *<B>注意事项：</B>
 *<pre>
 *  略
 *</pre>
 */
public class Scan
{
    /**
     * jar 包文件后缀
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    protected static final String JAR_FILE = ".jar";

    /**
     *　class 类文件后缀
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    protected static final String CLASS_FILE = ".class";

    /**
     * 扫描路径
     *<B>说明：</B>
     *<pre>
     *  逗号分割支持多个包
     *</pre>
     */
    protected String basePackage;

    /**
     * 当前类原始对象
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    protected ClassLoader clazz;

    /**
     * 扫描条件
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    protected ScanCondition scanCondition;

    /**
     * 扫描事件
     *<B>说明：</B>
     *<pre>
     * 略
     *</pre>
     */
    protected List<ScanHandler> handlers = new ArrayList<>();

    public Scan()
    {
        this.clazz = this.getClass().getClassLoader();
    }

    public Scan(String basePackage)
    {
        this.basePackage = basePackage;
        this.clazz = this.getClass().getClassLoader();
    }

    /**
     * 获取注册的事件对象
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    public List<ScanHandler> getHandlers()
    {
        return this.handlers;
    }

    /**
     * 注册扫描事件
     *<B>说明：</B>
     *<pre>
     * 略
     *</pre>
     */
    public void registerHandler(ScanHandler handler)
    {
        this.handlers.add(handler);
    }

    /**
     * 扫描资源文件
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    public List<String> scanResource(String basePath)
    {
        List<String> pathList = new ArrayList<>();
        if (this.isExistFile(basePath)) {
            pathList.add(basePath);
            return pathList;
        }

        // 开始递归扫描,至扫描文件目录,不扫描jar,class
        ResourcePath resourcePath = Container.getContainer().getResourcePath();
        this.scanCondition = resourcePath.getScanPath(basePath);
        this.doScanResource(this.scanCondition.getBasePath(),pathList,this.scanCondition.getReadAll());

        return pathList;
    }

    private void doScanResource(String baseResourcePath,List<String> pathList,boolean readAll)
    {
        List<String> names = null;
        names = readFromDirectory(baseResourcePath);

        for (String name : names) {
            String newBaseResourcePath = baseResourcePath + File.separator + name;
            File dirname = new File(newBaseResourcePath);
            if (this.isRuleFile(newBaseResourcePath)) {
                if (readAll == true && dirname.isDirectory()) {
                    doScanResource(newBaseResourcePath,pathList,readAll);
                } else {
                    pathList.add(newBaseResourcePath);
                }
            }
        }
    }

    /**
     * 扫描包文件
     *<B>说明：</B>
     *<pre>
     *  扫描.class,jar 后缀文件
     *</pre>
     * @throws Exception
     */
    public List<Class<?>> scanPackage() throws Exception
    {
        List<Class<?>> nameList = new ArrayList<>();
        String[] basePackages = basePackage.split(",");

        for (int i = 0;i < basePackages.length;i++) {
            doScanPackage(basePackages[i], nameList);
        }

        return nameList;
    }

    /**
     * 扫描包下面的类
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @param basePackage 包名
     * @param nameList 类存储列表
     * @throws Exception
     */
    private List<Class<?>> doScanPackage(String basePackage, List<Class<?>> nameList) throws Exception
    {
        String splashPath = this.dotToSplash(basePackage);
        // 获取类文件的绝对路径
        URL url = this.clazz.getResource(splashPath);
        String filePath = this.getRootPath(url);

        List<String> names = null;
        if (isJarFile(filePath)) {
            names = readFromJarFile(filePath, splashPath);
        } else {
            names = readFromDirectory(filePath);
        }

        String fullName;

        for (String name : names) {
            if (isClassFile(name)) {
                fullName = toFullyQualifiedName(name, basePackage);
                Class<?> className = Class.forName(fullName);
                nameList.add(className);
                this.addClassFile(className);
            } else {
                String newfilePath= filePath + File.separator + name;
                File dirname = new File(newfilePath);
                if (dirname.isDirectory()) {
                    String newBasePackage = basePackage + "." + name;
                    // 遇到目录,继续扫描目录
                    doScanPackage(newBasePackage, nameList);
                }
            }
        }

        return nameList;
    }

    /**
     * class 短名称转换成完整的包名
     *<B>说明：</B>
     *<pre>
     *  e.g., String -> java.lang.String
     *</pre>
     * @param shortName
     * @param basePackage
     */
    private String toFullyQualifiedName(String shortName, String basePackage)
    {
        StringBuilder sb = new StringBuilder(basePackage);
        sb.append('.');
        sb.append(this.trimExtension(shortName));

        return sb.toString();
    }

    /**
     * 读取jar 目录下的文件及目录
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @param jarPath jar 文件路径
     * @param splashedPackageName jar 文件路径
     */
    private List<String> readFromJarFile(String jarPath, String splashedPackageName) throws Exception
    {
        JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
        JarEntry entry = jarIn.getNextJarEntry();
        List<String> nameList = new ArrayList<>();
        while (null != entry) {
            String name = entry.getName();
            if (name.startsWith(splashedPackageName) && this.isClassFile(name)) {
                nameList.add(name);
            }

            entry = jarIn.getNextJarEntry();
        }

        return nameList;
    }

    /**
     * 添加class 文件名
     *<B>说明：</B>
     *<pre>
     *  类事件处理
     *</pre>
     */
    private void addClassFile(Class<?> clazz) throws Exception
    {
        // 执行事件
        for (ScanHandler handler : this.handlers) {
            handler.collect(clazz);
        }
    }

    /**
     * 读取指定目录下的所有文件包括目录
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @param path 文件路径
     * @return 文件名称列表
     */
    private List<String> readFromDirectory(String path)
    {
        File file = new File(path);
        String[] names = file.list();

        if (null == names) {
            return null;
        }

        return Arrays.asList(names);
    }

    /**
     * 判断是否class文件
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @param name 类路径
     * @return
     */
    private boolean isClassFile(String name)
    {
        return name.endsWith(CLASS_FILE);
    }

    /**
     * 判断是否jar 包
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @param name 类路径
     */
    private boolean isJarFile(String name)
    {
        return name.endsWith(JAR_FILE);
    }

    /**
     * 判断文件是否存在
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    private boolean isExistFile(String name)
    {
        File file = new File(name);
        if (file.exists()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 是否满足规则的文件
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     */
    private boolean isRuleFile(String name)
    {

        Matcher matcher = Pattern.compile(this.scanCondition.getRule()).matcher(name);

        if (matcher.matches()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取资源的完整路径
     *<B>说明：</B>
     *<pre>
     *  "file:/home/whf/cn/fh" -> "/home/whf/cn/fh"
     *  "jar:file:/home/whf/foo.jar!cn/fh" -> "/home/whf/foo.jar"
     *</pre>
     * @param url 资源路径
     */
    private String getRootPath(URL url)
    {
        String fileUrl = url.getFile();
        int pos = fileUrl.indexOf('!');

        if (-1 == pos) {
            return fileUrl;
        }

        return fileUrl.substring(5, pos);
    }

    /**
     * 类或包命令空间字符分隔符"." 替换成"/"
     *<B>说明：</B>
     *<pre>
     *  "cn.fh.lightning" -> "cn/fh/lightning"
     *</pre>
     * @param name 类路径
     */
    private String dotToSplash(String name)
    {
        return name.replaceAll("\\.", "/");
    }

    /**
     * 去掉后缀
     *<B>说明：</B>
     *<pre>
     *  "Apple.class" -> "Apple"
     *</pre>
     * @param name 类路径
     */
    private String trimExtension(String name)
    {
        int pos = name.indexOf('.');
        if (-1 != pos) {
            return name.substring(0, pos);
        }

        return name;
    }
}
